import ast
from typing import Dict, List, Set, Optional
import os
import re


class DependencyAnalyzer:
    """代码依赖关系分析器"""

    def __init__(self):
        """初始化依赖分析器"""
        self.dependencies = {}  # 存储文件依赖信息
        self.imports = {}  # 存储文件导入信息
        self.definitions = {}  # 存储文件中定义的内容

    def parse_file(self, file_path: str, file_content: str) -> None:
        """解析Python文件中的导入和定义

        Args:
            file_path: 文件路径
            file_content: 文件内容

        Returns:
            None
        """
        try:
            tree = ast.parse(file_content)

            # 分析导入
            imports = []
            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for name in node.names:
                        imports.append(name.name)
                elif isinstance(node, ast.ImportFrom):
                    if node.module:
                        imports.append(node.module)

            # 分析定义
            definitions = []
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    definitions.append(node.name)
                elif isinstance(node, ast.FunctionDef):
                    definitions.append(node.name)

            self.imports[file_path] = imports
            self.definitions[file_path] = definitions
            self.dependencies[file_path] = {
                "imports": imports,
                "definitions": definitions
            }

        except SyntaxError:
            print(f"无法解析文件: {file_path}")

    def parse_project(self, project_path: str, exclude_dirs: Optional[List[str]] = None) -> None:
        """解析整个项目的Python文件

        Args:
            project_path: 项目根目录路径
            exclude_dirs: 要排除的目录列表

        Returns:
            None
        """
        if exclude_dirs is None:
            exclude_dirs = ['.git', '__pycache__', 'venv', 'env', '.venv', 'node_modules']

        for root, dirs, files in os.walk(project_path):
            # 排除指定目录
            dirs[:] = [d for d in dirs if d not in exclude_dirs]

            for file in files:
                if file.endswith('.py'):
                    file_path = os.path.join(root, file)
                    rel_path = os.path.relpath(file_path, project_path)

                    try:
                        with open(file_path, 'r', encoding='utf-8') as f:
                            file_content = f.read()

                        self.parse_file(rel_path, file_content)
                    except Exception as e:
                        print(f"处理文件 {file_path} 时出错: {e}")

    def build_dependency_graph(self) -> Dict[str, List[str]]:
        """构建依赖关系图

        Returns:
            文件到其依赖的其他文件的映射
        """
        graph = {}

        # 文件路径规范化函数
        def normalize_path(path):
            # 移除.py后缀并将路径分隔符转换为点
            return path.replace('.py', '').replace('/', '.').replace('\\', '.')

        # 先构建模块名到文件路径的映射
        module_to_file = {}
        for file_path in self.definitions:
            module_name = normalize_path(file_path)
            module_to_file[module_name] = file_path

            # 处理子模块
            parts = module_name.split('.')
            for i in range(1, len(parts) + 1):
                submodule = '.'.join(parts[:i])
                if submodule not in module_to_file:
                    module_to_file[submodule] = file_path

        # 构建依赖图
        for file_path in self.imports:
            graph[file_path] = []

            for imported in self.imports[file_path]:
                # 检查是否直接匹配模块名
                if imported in module_to_file:
                    dependent_file = module_to_file[imported]
                    if dependent_file != file_path:  # 避免自引用
                        graph[file_path].append(dependent_file)
                else:
                    # 尝试匹配可能的子模块
                    for module_name, module_file in module_to_file.items():
                        if imported.startswith(module_name + '.'):
                            if module_file != file_path:  # 避免自引用
                                graph[file_path].append(module_file)
                                break

        return graph

    def get_related_files(self, file_path: str, graph: Optional[Dict[str, List[str]]] = None) -> Set[str]:
        """获取与指定文件相关的所有文件

        Args:
            file_path: 目标文件路径
            graph: 依赖图，如果为None则使用build_dependency_graph()构建

        Returns:
            相关文件的集合
        """
        if graph is None:
            graph = self.build_dependency_graph()

        related = set()

        # 查找依赖此文件的文件
        for f, deps in graph.items():
            if file_path in deps:
                related.add(f)

        # 查找此文件依赖的文件
        if file_path in graph:
            related.update(graph[file_path])

        return related
